﻿#region Copyright
//       Copyright (c) 2009 - Antoine Jaussoin - http://www.jaussoin.com/blog/
// 
//        This program is free software: you can redistribute it and/or modify
//        it under the terms of the GNU General Public License as published by
//        the Free Software Foundation, either version 3 of the License, or
//        (at your option) any later version.
// 
//        This program is distributed in the hope that it will be useful,
//        but WITHOUT ANY WARRANTY; without even the implied warranty of
//        MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//        GNU General Public License for more details.
// 
//        You should have received a copy of the GNU General Public License
//        along with this program.  If not, see <http://www.gnu.org/licenses/>.
#endregion
using System;
using System.Globalization;
using System.Text;
using System.Text.RegularExpressions;

namespace FT.Architecture.Utils.ExtensionMethods
{
    /// <summary>
    /// Extension Methods for the string class
    /// </summary>
    public static class StringExtensions
    {
        private static readonly Regex FullTrimStartRegex = new Regex(@"\A[\w\d].*", RegexOptions.Multiline);
        private static readonly Regex FullTrimEndRegex = new Regex(@".*[\w\d\.,;]\Z", RegexOptions.Multiline);


        /// <summary>
        /// Abbreviates a string to the specified number of characters.
        /// If the string is shorter than this, the whole string is returned
        /// </summary>
        /// <param name="data">the string to shorten</param>
        /// <param name="length">the number of characters at the start of the string to return, including the suffix</param>
        /// <param name="suffix">the suffix to add to the end of the string if it has been abbreviated</param>
        /// <returns>The abbreviated string</returns>
        public static string Abbreviate(this string data, int length, string suffix)
        {
            //obviously avoid nulls
            if (data == null) return null;

            // Can't have a total length greater than the suffix
            if (length < suffix.Length)
                throw new Exception(string.Format("You can't have a length ({0}) that is smaller than the suffix ('{1}')", length, suffix));

            if (data.Length <= length)
                return data;

            return data.Substring(0, length - suffix.Length) + suffix;
        }


        /// <summary>
        /// Remove any character at the end of the string that are not regular (a-z, 0-9, some punctuation) characters
        /// </summary>
        /// <param name="source"></param>
        /// <returns></returns>
        public static string FullTrimEnd(this string source)
        {
            if (string.IsNullOrEmpty(source))
                return source;

            var result = new StringBuilder(source);

            while (result.Length > 0)
            {
                string resultStr = result.ToString();

                if (resultStr[resultStr.Length - 1] != '\n')
                {
                    if (FullTrimEndRegex.Match(resultStr[resultStr.Length - 1].ToString()).Success)
                        return resultStr;
                }


                result.Remove(result.Length - 1, 1);
            }

            return string.Empty;
        }

        /// <summary>
        /// Remove any character at the start of the string that are not regular (a-z, 0-9, some punctuation) characters
        /// </summary>
        /// <param name="source"></param>
        /// <returns></returns>
        public static string FullTrimStart(this string source)
        {
            if (string.IsNullOrEmpty(source))
                return source;

            var result = new StringBuilder(source);

            while (result.Length > 0)
            {
                string resultStr = result.ToString();

                if (FullTrimStartRegex.Match(resultStr[0].ToString()).Success)
                    return resultStr;

                result.Remove(0, 1);
            }

            return string.Empty;
        }

        /// <summary>
        /// Remove any character at the start and the end of the string that are not regular (a-z, 0-9, some punctuation) characters
        /// </summary>
        /// <param name="source"></param>
        /// <returns></returns>
        public static string FullTrim(this string source)
        {
            return source.FullTrimStart().FullTrimEnd();
        }

        /// <summary>
        /// Capitalise the first letter in each word
        /// </summary>
        /// <param name="input"></param>
        /// <returns></returns>
        public static string ToTitleCaseEachWord(this string input)
        {
            return CultureInfo.CurrentCulture.TextInfo.ToTitleCase(input.ToLower());
        }

        /// <summary>
        /// Transform a string such as "DailyAbsoluteReturn" or "DAILY_ABSOLUTE_RETURN" to "Daily Absolute Return".
        /// Very useful to get a friendly display of the value of an enumeration for example.
        /// </summary>
        /// <param name="aString"></param>
        /// <param name="style">Style of the enumeration</param>
        /// <returns></returns>
        public static string GetFriendlyDisplay(this string aString, EnumerationStyle style)
        {
            switch (style)
            {
                case EnumerationStyle.UpperAndLowerCaseNoUnderscore:
                    return GetFriendlyDisplayUpperAndLower(aString);
                case EnumerationStyle.UpperCaseWithUnderscore:
                    return GetFriendlyDisplayUpperAndUnderscore(aString);
                default:
                    throw new ArgumentOutOfRangeException("style");
            }
        }

        /// <summary>
        /// Transform a string such as "DailyAbsoluteReturn" to "Daily Absolute Return".
        /// Very useful to get a friendly display of the value of an enumeration for example.
        /// </summary>
        /// <param name="aString"></param>
        /// <returns></returns>
        public static string GetFriendlyDisplay(this string aString)
        {
            return GetFriendlyDisplay(aString, EnumerationStyle.UpperAndLowerCaseNoUnderscore);
        }

        /// <summary>
        /// Transforms "MY_NAME_IS_BOB" into "My Name Is Bob"
        /// </summary>
        /// <param name="str"></param>
        /// <returns></returns>
        private static string GetFriendlyDisplayUpperAndUnderscore(string str)
        {
            if (string.IsNullOrEmpty(str))
                return str;

            string[] words = str.Split('_');
            var b = new StringBuilder();

            foreach (string word in words)
            {
                b.Append(word.ToLower().ToTitleCaseEachWord());
                b.Append(" ");
            }

            b.Remove(b.Length - 1, 1);

            return b.ToString();
        }

        /// <summary>
        /// Transforms "MyNameIsBob" into "My Name Is Bob"
        /// </summary>
        /// <param name="aString"></param>
        /// <returns></returns>
        private static string GetFriendlyDisplayUpperAndLower(string aString)
        {
            var b = new StringBuilder();
            var cur = new StringBuilder();

            for (int i = 0; i < aString.Length; i++)
            {
                char prev = i == 0 ? '\0' : aString[i - 1];
                char next = i + 1 == aString.Length ? '\0' : aString[i + 1];
                char c = aString[i];

                if (cur.Length == 0)
                    cur.Append(c);
                else
                {
                    if (IsNewWordChar(c, prev, next))
                    {
                        b.Append(cur).Append(' ');
                        cur = new StringBuilder();
                        cur.Append(c);
                    }
                    else
                    {
                        cur.Append(c);
                    }
                }
            }

            b.Append(cur);

            return b.ToString();
        }

        /// <summary>
        /// Returns <see langword="true"/> if the character is a digit or a capital letter
        /// </summary>
        /// <param name="c"></param>
        /// <returns></returns>
        private static bool IsDigitOrUpper(char c)
        {
            return char.IsDigit(c) || char.IsUpper(c);
        }

        /// <summary>
        /// Returns <see langword="true"/> if the current character is the start of a new word, based on the previous and the next character
        /// </summary>
        /// <param name="current"></param>
        /// <param name="previous"></param>
        /// <param name="next"></param>
        /// <returns></returns>
        private static bool IsNewWordChar(char current, char previous, char next)
        {
            if (previous == '\0')
                return true;

            if (IsDigitOrUpper(current) && !IsDigitOrUpper(previous))
                return true;

            if (char.IsDigit(current) && char.IsUpper(previous))
                return true;

            if (char.IsDigit(previous) && char.IsUpper(current))
                return true;

            return next != '\0' && char.IsUpper(current) && char.IsUpper(previous) && char.IsLower(next);
        }
    }
}